﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using System.Reflection;

namespace TimScripts.BF4Rcon.Test
{
    public class MapTests
    {
        [Test]
        public void MapNamePropertiesGetPopulated()
        {
            GameMode mode = GameMode.Unknown;
            var maps = Enum.GetValues(typeof(MapName)).Cast<MapName>().Select(n => new Map(n, mode));

            Assert.True(maps.Count() > 0);

            foreach (Map m in maps)
            {
                Assert.True(HasMatchingMapAttribute(m));
            }
        }

        [Test]
        public void GameModePropertiesGetPopulated()
        {
            MapName name = MapName.Unknown;
            var maps = Enum.GetValues(typeof(GameMode)).Cast<GameMode>().Select(gm => new Map(name, gm));

            Assert.True(maps.Count() > 0);

            foreach (Map m in maps)
            {
                Assert.True(HasMatchingModeAttribute(m));
            }
        }

        [Test]
        public void MapNamesAreParsedCorrectly()
        {
            var maps = GetAttributes<MapName, MapInformationAttribute>();
            Map map;
            string mode = string.Empty;

            foreach (var attrib in maps)
            {
                map = Map.Parse(attrib.RawName, mode);

                Assert.True(MapMatchesAttribute(map, attrib));
            }
        }

        [Test]
        public void MapNamesAreParsedCaseInsensitively()
        {
            var maps = GetAttributes<MapName, MapInformationAttribute>();
            Map map;
            string mode = string.Empty;

            foreach (var attrib in maps)
            {
                map = Map.Parse(attrib.RawName.ToUpperInvariant(), mode);

                Assert.True(MapMatchesAttribute(map, attrib));
            }
        }

        [Test]
        public void GameModesAreParsedCorrectly()
        {
            var modes = GetAttributes<GameMode, GameModeInformationAttribute>();
            Map map;
            string mapName = string.Empty;

            foreach (var attrib in modes)
            {
                map = Map.Parse(mapName, attrib.RawName);

                Assert.True(ModeMatchesAttribute(map, attrib));
            }
        }

        [Test]
        public void GameModesAreParsedCaseInsensitively()
        {
            var modes = GetAttributes<GameMode, GameModeInformationAttribute>();
            Map map;
            string mapName = string.Empty;

            foreach (var attrib in modes)
            {
                map = Map.Parse(mapName, attrib.RawName.ToUpperInvariant());

                Assert.True(ModeMatchesAttribute(map, attrib));
            }
        }

        [Test]
        public void UnspecifiedRoundsIsNegativeOne()
        {
            Map map = new Map(MapName.AltaiRange, GameMode.AirSuperiority);

            Assert.AreEqual(-1, map.RoundsToPlay);
        }

        [Test]
        public void RoundsIsAssignedCorrectly()
        {
            int rounds = 69;
            Map map = new Map(MapName.AltaiRange, GameMode.AirSuperiority, rounds);

            Assert.AreEqual(rounds, map.RoundsToPlay);
        }

        [Test]
        public void RoundsMustBeGreaterThanZero()
        {
            int rounds = -1;
            Assert.Throws<ArgumentOutOfRangeException>(() => new Map(MapName.AltaiRange, GameMode.AirSuperiority, rounds));
        }

        [Test]
        public void InvalidMapNameIsSetAsUnknown()
        {
            string mapName = "fakemap";
            string gameMode = "dontmatter";
            Map map = Map.Parse(mapName, gameMode);

            Assert.AreEqual(MapName.Unknown, map.MapName);
        }

        [Test]
        public void InvalidGameModeIsSetAsUnknown()
        {
            string mapName = "dontmatter";
            string gameMode = "fakemode";
            Map map = Map.Parse(mapName, gameMode);

            Assert.AreEqual(GameMode.Unknown, map.GameMode);
        }

        private bool HasMatchingMapAttribute(Map map)
        {
            IEnumerable<MapInformationAttribute> attribs = GetAttributes<MapName, MapInformationAttribute>();

            foreach (var attrib in attribs)
            {
                if (MapMatchesAttribute(map, attrib))
                    return true;
            }

            return false;
        }

        private bool HasMatchingModeAttribute(Map map)
        {
            IEnumerable<GameModeInformationAttribute> attribs = GetAttributes<GameMode, GameModeInformationAttribute>();

            foreach (var attrib in attribs)
            {
                if (ModeMatchesAttribute(map, attrib))
                    return true;
            }

            return false;
        }

        private static bool ModeMatchesAttribute(Map map, GameModeInformationAttribute attrib)
        {
            return map.RawModeName.Equals(attrib.RawName) && map.DescriptiveModeName.Equals(attrib.DescriptiveName) &&
                                map.Commanders == attrib.Commanders && map.Players == attrib.Players && map.Spectators == attrib.Spectators;
        }

        private static bool MapMatchesAttribute(Map map, MapInformationAttribute attrib)
        {
            return map.RawMapName.Equals(attrib.RawName) && map.DescriptiveMapName.Equals(attrib.DescriptiveName);
        }

        private IEnumerable<TAttrib> GetAttributes<TEnum, TAttrib>()
        {
            FieldInfo[] fields;
            object[] attributes;

            foreach (string name in Enum.GetNames(typeof(TEnum)))
            {
                fields = typeof(TEnum).GetFields();

                foreach (FieldInfo f in fields)
                {
                    attributes = f.GetCustomAttributes(typeof(TAttrib), false);

                    if (attributes.Length == 1)
                        yield return (TAttrib)attributes[0];
                }
            }
        }
    }
}
